sbt-native-imageで実行可能なバイナリを作成する
はじめに
graalvmをつかって実行可能なバイナリを作成できるsbt プラグイン sbt-native-image を試してみました。
ネイティブ化したプログラム
今回ネイティブ化したコードは以下のフィボナッチ数を求めるプログラムです。
package example import zio._ import zio.console._ object Fib extends zio.App { def fib[R](n: Long):ZIO[R, Nothing, Long] = { if (n == 0 || n == 1) ZIO.succeed(n) else for { a <- ZIO.unit *> fib(n-1) b <- ZIO.unit *> fib(n-2) } yield a + b } def run(args: List[String]): URIO[ZEnv, ExitCode] = (for { n <- ZIO.fromOption(args.headOption.flatMap(_.toLongOption)) ans <- fib(n) _ <- putStrLn(ans.toString) } yield ()).exitCode }
build.sbtは以下の通り
name := "sbt-native-image-example" version := "0.1" scalaVersion := "2.13.3" libraryDependencies += "dev.zio" %% "zio" % "1.0.1"
インストール(project/plugins.sbt)]
例によってプラグインを下記のように指定します。
addSbtPlugin("org.scalameta" % "sbt-native-image" % "0.2.1")
ビルド
ネイティブイメージを作成するにはnativeImageタスクでビルドします。初回はgraalvmのダウンロードをするため10分以上かかります。
macOS上でビルドしていた場合sayコマンドの音声で「Native image ready!」と知らせてくれます。
実行ログ(1回目)
~/s/g/c/b/sbt-native-image-example ❯❯❯ sbt [info] welcome to sbt 1.3.13 (Amazon.com Inc. Java 1.8.0_242) [info] loading settings for project global-plugins from idea.sbt,plugins.sbt ... [info] loading global plugins from /Users/sasaki.kazuhiro/.sbt/1.0/plugins [info] loading settings for project sbt-native-image-example-build from plugins.sbt ... [info] loading project definition from /Users/sasaki.kazuhiro/src/github.com/cm-kazup0n/blog-examples/sbt-native-image-example/project [info] loading settings for project sbt-native-image-example from build.sbt ... [info] set current project to sbt-native-image-example (in build file:/Users/sasaki.kazuhiro/src/github.com/cm-kazup0n/blog-examples/sbt-native-image-example/) [info] sbt server started at local:///Users/sasaki.kazuhiro/.sbt/1.0/server/f15941dddccd2edc712e/sock sbt:sbt-native-image-example> nativeImage [info] Compiling 1 Scala source to /Users/sasaki.kazuhiro/src/github.com/cm-kazup0n/blog-examples/sbt-native-image-example/target/scala-2.13/classes ... Downloading https://github.com/graalvm/graalvm-ce-builds/releases/download/vm-20.1.0/graalvm-ce-java11-darwin-amd64-20.1.0.tar.gz Still downloading: https://github.com/graalvm/graalvm-ce-builds/releases/download/vm-20.1.0/graalvm-ce-java11-darwin-amd64-20.1.0.tar.gz (59.04 %, 253120899 / 428722016) Still downloading: https://github.com/graalvm/graalvm-ce-builds/releases/download/vm-20.1.0/graalvm-ce-java11-darwin-amd64-20.1.0.tar.gz (99.40 %, 426168633 / 428722016) Downloaded https://github.com/graalvm/graalvm-ce-builds/releases/download/vm-20.1.0/graalvm-ce-java11-darwin-amd64-20.1.0.tar.gz Extracting /Users/sasaki.kazuhiro/Library/Caches/Coursier/v1/https/github.com/graalvm/graalvm-ce-builds/releases/download/vm-20.1.0/graalvm-ce-java11-darwin-amd64-20.1.0.tar.gz in /Users/sasaki.kazuhiro/Library/Caches/Coursier/jvm/[email protected] Done Downloading: Component catalog from www.graalvm.org Processing Component: Native Image Downloading: Component native-image: Native Image from github.com Installing new component: Native Image (org.graalvm.native-image, version 20.1.0) [info] /Users/sasaki.kazuhiro/Library/Caches/Coursier/jvm/[email protected]/Contents/Home/bin/native-image -cp /Users/sasaki.kazuhiro/src/github.com/cm-kazup0n/blog-examples/sbt-native-image-example/target/native-image-internal/manifest.jar example.Fib /Users/sasaki.kazuhiro/src/github.com/cm-kazup0n/blog-examples/sbt-native-image-example/target/native-image/sbt-native-image-example Build on Server(pid: 10427, port: 60389)* [/Users/sasaki.kazuhiro/src/github.com/cm-kazup0n/blog-examples/sbt-native-image-example/target/native-image/sbt-native-image-example:10427] classlist: 2,600.36 ms, 0.96 GB [/Users/sasaki.kazuhiro/src/github.com/cm-kazup0n/blog-examples/sbt-native-image-example/target/native-image/sbt-native-image-example:10427] (cap): 4,170.49 ms, 0.96 GB [/Users/sasaki.kazuhiro/src/github.com/cm-kazup0n/blog-examples/sbt-native-image-example/target/native-image/sbt-native-image-example:10427] setup: 5,685.32 ms, 0.96 GB [/Users/sasaki.kazuhiro/src/github.com/cm-kazup0n/blog-examples/sbt-native-image-example/target/native-image/sbt-native-image-example:10427] (clinit): 590.75 ms, 1.72 GB [/Users/sasaki.kazuhiro/src/github.com/cm-kazup0n/blog-examples/sbt-native-image-example/target/native-image/sbt-native-image-example:10427] (typeflow): 8,507.30 ms, 1.72 GB [/Users/sasaki.kazuhiro/src/github.com/cm-kazup0n/blog-examples/sbt-native-image-example/target/native-image/sbt-native-image-example:10427] (objects): 9,635.14 ms, 1.72 GB [/Users/sasaki.kazuhiro/src/github.com/cm-kazup0n/blog-examples/sbt-native-image-example/target/native-image/sbt-native-image-example:10427] (features): 405.95 ms, 1.72 GB [/Users/sasaki.kazuhiro/src/github.com/cm-kazup0n/blog-examples/sbt-native-image-example/target/native-image/sbt-native-image-example:10427] analysis: 19,657.76 ms, 1.72 GB [/Users/sasaki.kazuhiro/src/github.com/cm-kazup0n/blog-examples/sbt-native-image-example/target/native-image/sbt-native-image-example:10427] universe: 461.65 ms, 1.72 GB Warning: Reflection method java.lang.Class.getDeclaredMethod invoked at zio.internal.stacktracer.impl.AkkaLineNumbers$.getStreamForLambda(AkkaLineNumbers.scala:201) Warning: Aborting stand-alone image build due to reflection use without configuration. Warning: Use -H:+ReportExceptionStackTraces to print stacktrace of underlying exception Build on Server(pid: 10427, port: 60389) [/Users/sasaki.kazuhiro/src/github.com/cm-kazup0n/blog-examples/sbt-native-image-example/target/native-image/sbt-native-image-example:10427] classlist: 92.97 ms, 2.21 GB [/Users/sasaki.kazuhiro/src/github.com/cm-kazup0n/blog-examples/sbt-native-image-example/target/native-image/sbt-native-image-example:10427] (cap): 2,152.91 ms, 2.21 GB [/Users/sasaki.kazuhiro/src/github.com/cm-kazup0n/blog-examples/sbt-native-image-example/target/native-image/sbt-native-image-example:10427] setup: 2,455.07 ms, 2.21 GB [/Users/sasaki.kazuhiro/src/github.com/cm-kazup0n/blog-examples/sbt-native-image-example/target/native-image/sbt-native-image-example:10427] (clinit): 151.36 ms, 2.21 GB [/Users/sasaki.kazuhiro/src/github.com/cm-kazup0n/blog-examples/sbt-native-image-example/target/native-image/sbt-native-image-example:10427] (typeflow): 2,721.18 ms, 2.21 GB [/Users/sasaki.kazuhiro/src/github.com/cm-kazup0n/blog-examples/sbt-native-image-example/target/native-image/sbt-native-image-example:10427] (objects): 3,078.55 ms, 2.21 GB [/Users/sasaki.kazuhiro/src/github.com/cm-kazup0n/blog-examples/sbt-native-image-example/target/native-image/sbt-native-image-example:10427] (features): 94.06 ms, 2.21 GB [/Users/sasaki.kazuhiro/src/github.com/cm-kazup0n/blog-examples/sbt-native-image-example/target/native-image/sbt-native-image-example:10427] analysis: 6,192.40 ms, 2.21 GB [/Users/sasaki.kazuhiro/src/github.com/cm-kazup0n/blog-examples/sbt-native-image-example/target/native-image/sbt-native-image-example:10427] universe: 211.55 ms, 2.21 GB [/Users/sasaki.kazuhiro/src/github.com/cm-kazup0n/blog-examples/sbt-native-image-example/target/native-image/sbt-native-image-example:10427] (parse): 726.45 ms, 2.29 GB [/Users/sasaki.kazuhiro/src/github.com/cm-kazup0n/blog-examples/sbt-native-image-example/target/native-image/sbt-native-image-example:10427] (inline): 1,246.53 ms, 2.29 GB [/Users/sasaki.kazuhiro/src/github.com/cm-kazup0n/blog-examples/sbt-native-image-example/target/native-image/sbt-native-image-example:10427] (compile): 6,047.58 ms, 3.22 GB [/Users/sasaki.kazuhiro/src/github.com/cm-kazup0n/blog-examples/sbt-native-image-example/target/native-image/sbt-native-image-example:10427] compile: 8,400.81 ms, 3.22 GB [/Users/sasaki.kazuhiro/src/github.com/cm-kazup0n/blog-examples/sbt-native-image-example/target/native-image/sbt-native-image-example:10427] image: 873.75 ms, 3.22 GB [/Users/sasaki.kazuhiro/src/github.com/cm-kazup0n/blog-examples/sbt-native-image-example/target/native-image/sbt-native-image-example:10427] write: 315.83 ms, 3.22 GB [/Users/sasaki.kazuhiro/src/github.com/cm-kazup0n/blog-examples/sbt-native-image-example/target/native-image/sbt-native-image-example:10427] [total]: 18,610.73 ms, 3.22 GB Warning: Image '/Users/sasaki.kazuhiro/src/github.com/cm-kazup0n/blog-examples/sbt-native-image-example/target/native-image/sbt-native-image-example' is a fallback image that requires a JDK for execution (use --no-fallback to suppress fallback image generation and to print more detailed information why a fallback image was necessary). [info] Native image ready! [info] /Users/sasaki.kazuhiro/src/github.com/cm-kazup0n/blog-examples/sbt-native-image-example/target/native-image/sbt-native-image-example [success] Total time: 683 s (11:23)
オプションの指定
上記の実行結果ログを見ると以下のようにJDKをつかったイメージが作成されているのが分かります。メッセージで示されているように -no-fallback
を指定して再実行してみます。
- Warning: Image '/Users/sasaki.kazuhiro/src/github.com/cm-kazup0n/blog-examples/sbt-native-image-example/target/native-image/sbt-native-image-example' is a fallback image that requires a JDK for execution (use --no-fallback to suppress fallback image generation and to print more detailed information why a fallback image was necessary).
オプションはnativeImageOptions
に指定します。
nativeImageOptions ++= List("--no-fallback")
実行ログ(2回目)
2回目の実行結果は以下の通りです。
sbt:sbt-native-image-example> nativeImage [info] Compiling 1 Scala source to /Users/sasaki.kazuhiro/src/github.com/cm-kazup0n/blog-examples/sbt-native-image-example/target/scala-2.13/classes ... [info] /Users/sasaki.kazuhiro/Library/Caches/Coursier/jvm/[email protected]/Contents/Home/bin/native-image -cp /Users/sasaki.kazuhiro/src/github.com/cm-kazup0n/blog-examples/sbt-native-image-example/target/native-image-internal/manifest.jar --no-fallback example.Fib /Users/sasaki.kazuhiro/src/github.com/cm-kazup0n/blog-examples/sbt-native-image-example/target/native-image/sbt-native-image-example Build on Server(pid: 29968, port: 56106)* [/Users/sasaki.kazuhiro/src/github.com/cm-kazup0n/blog-examples/sbt-native-image-example/target/native-image/sbt-native-image-example:29968] classlist: 2,565.24 ms, 0.96 GB [/Users/sasaki.kazuhiro/src/github.com/cm-kazup0n/blog-examples/sbt-native-image-example/target/native-image/sbt-native-image-example:29968] (cap): 4,323.05 ms, 0.96 GB [/Users/sasaki.kazuhiro/src/github.com/cm-kazup0n/blog-examples/sbt-native-image-example/target/native-image/sbt-native-image-example:29968] setup: 5,702.13 ms, 0.96 GB [/Users/sasaki.kazuhiro/src/github.com/cm-kazup0n/blog-examples/sbt-native-image-example/target/native-image/sbt-native-image-example:29968] (clinit): 547.54 ms, 1.72 GB [/Users/sasaki.kazuhiro/src/github.com/cm-kazup0n/blog-examples/sbt-native-image-example/target/native-image/sbt-native-image-example:29968] (typeflow): 8,567.28 ms, 1.72 GB [/Users/sasaki.kazuhiro/src/github.com/cm-kazup0n/blog-examples/sbt-native-image-example/target/native-image/sbt-native-image-example:29968] (objects): 9,627.95 ms, 1.72 GB [/Users/sasaki.kazuhiro/src/github.com/cm-kazup0n/blog-examples/sbt-native-image-example/target/native-image/sbt-native-image-example:29968] (features): 417.20 ms, 1.72 GB [/Users/sasaki.kazuhiro/src/github.com/cm-kazup0n/blog-examples/sbt-native-image-example/target/native-image/sbt-native-image-example:29968] analysis: 19,631.94 ms, 1.72 GB [/Users/sasaki.kazuhiro/src/github.com/cm-kazup0n/blog-examples/sbt-native-image-example/target/native-image/sbt-native-image-example:29968] universe: 453.48 ms, 1.72 GB [/Users/sasaki.kazuhiro/src/github.com/cm-kazup0n/blog-examples/sbt-native-image-example/target/native-image/sbt-native-image-example:29968] (parse): 1,295.92 ms, 2.30 GB [/Users/sasaki.kazuhiro/src/github.com/cm-kazup0n/blog-examples/sbt-native-image-example/target/native-image/sbt-native-image-example:29968] (inline): 1,597.05 ms, 2.30 GB [/Users/sasaki.kazuhiro/src/github.com/cm-kazup0n/blog-examples/sbt-native-image-example/target/native-image/sbt-native-image-example:29968] (compile): 9,656.70 ms, 3.29 GB [/Users/sasaki.kazuhiro/src/github.com/cm-kazup0n/blog-examples/sbt-native-image-example/target/native-image/sbt-native-image-example:29968] compile: 13,209.95 ms, 3.29 GB [/Users/sasaki.kazuhiro/src/github.com/cm-kazup0n/blog-examples/sbt-native-image-example/target/native-image/sbt-native-image-example:29968] image: 1,394.82 ms, 3.29 GB [/Users/sasaki.kazuhiro/src/github.com/cm-kazup0n/blog-examples/sbt-native-image-example/target/native-image/sbt-native-image-example:29968] write: 808.47 ms, 4.73 GB [/Users/sasaki.kazuhiro/src/github.com/cm-kazup0n/blog-examples/sbt-native-image-example/target/native-image/sbt-native-image-example:29968] [total]: 44,011.19 ms, 4.73 GB [info] Native image ready! [info] /Users/sasaki.kazuhiro/src/github.com/cm-kazup0n/blog-examples/sbt-native-image-example/target/native-image/sbt-native-image-example [success] Total time: 53 s
起動時間の比較
作成したバイナリ実行時間をsbt-assemblyで作成したFatJarをJVMで実行した結果と比較してみます。 起動時間がかなり短縮されているのが分かります。
~/s/g/c/b/sbt-native-image-example ❯❯❯ java -version openjdk version "1.8.0_242" OpenJDK Runtime Environment Corretto-8.242.08.1 (build 1.8.0_242-b08) OpenJDK 64-Bit Server VM Corretto-8.242.08.1 (build 25.242-b08, mixed mode) ~/s/g/c/b/sbt-native-image-example ❯❯❯ time ./target/native-image/sbt-native-image-example 5 5 0.01 real 0.00 user 0.00 sys ~/s/g/c/b/sbt-native-image-example ❯❯❯ time java -jar target/scala-2.13/sbt-native-image-example-assembly-0.1.jar 5 5 0.83 real 1.46 user 0.20 sys
まとめ
軽く触ってみましたがプラグインの導入だけで(graalvmのインストールをしなくても)バイナリの作成が行えるのは手軽でいいのかなと思いました。